/**
 * Created by zhangguoyu on 16/4/16.
 */

var util = require('util');
var utils = require('../utils/Utils');
var co = require('co');
var Oop = require('../lang/Oop');
var LogUtils = require('../utils/log/LogUtils');

var ModelManager = function (name, modelClass) {

  var self = this;

  self.$name = name;
  self.$model = modelClass;
  self.$rels = [];

  self._getEventName = function (options, def) {
    if (options === undefined) {
      return def;
    }
    return options.notifiedEvent || def || '';
  };

  Oop.call(self);
};

util.inherits(ModelManager, Oop);

/**
 * 注册其他关联的model manager
 * @param other
 */
ModelManager.prototype.register = function (other) {
  var contained = false;
  var i = this.$rels.length;
  while (i--) {
    if (this.$rels[i] === other) {
      contained = true;
      break;
    }
  }
  if (!contained) {
    this.$rels.push(other)
  }
};

ModelManager.prototype.notify = function (event) {
  var self = this;
  var i = self.$rels.length;
  var args = arguments;

  if (i < 1) {
    return;
  }

  return new Promise(function (resolve, reject) {
    co(function * () {
      var argsNotified = [self.$name, event];
      var argsEvent = utils.toArray(args);
      var argsConverted = self.preNotify.apply(self, argsEvent);
      if (utils.isPromise(argsConverted)) {
        argsConverted = yield argsConverted;
      }
      if (argsConverted) {
        argsEvent = argsConverted;
      }
      if (argsEvent) {
        argsNotified = argsNotified.concat(argsEvent);
      }
      while(i--) {
        var rel = self.$rels[i];
        var result = rel.onEvent.apply(rel, argsNotified);
        if (utils.isPromise(result)) {
          yield result;
        }
      }
      resolve(true);
    }).catch(function (err) {
      reject(err);
    });
  });

};

ModelManager.prototype.preNotify = function () {
  return this.$preNotify.apply(this, utils.toArray(arguments));
};

ModelManager.prototype.$preNotify = function (event) {
  var argsEvent = utils.toArray(arguments);
  return argsEvent.length > 1 ? argsEvent.slice(1) : []
};

ModelManager.prototype.onEvent = function (model, event, result) {
};

ModelManager.prototype.unregister = function (other) {
  var index = -1;
  var i = this.$rels.length;
  while (i--) {
    if (this.$rels[i] === other) {
      index = i;
      break;
    }
  }
  if (index > -1) {
    this.$rels.splice(index, 1);
  }
};

ModelManager.prototype.batchInsert = function (docs, options, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    co(function * () {

      if (utils.isFunction(options)) {
        func = options;
        options = {};
      }

      if (!options) {
        options = {};
      }

      if (!docs || !docs.length) {
        var err = new Error('要存储的文档列表是空的');
        utils.callFunc(func, self, err);
        reject(err);
      }

      var result = yield self.$model.insertMany(docs, func);
      var eventResult = self.notify.call(self, self._getEventName(options, 'insert'), result);
      if (utils.isPromise(eventResult)) {
        yield eventResult;
      }
      utils.callFunc(func, self, undefined, result);
      resolve(result);
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err);
    });
  });
};

ModelManager.prototype.insert = function (doc, options, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    co(function * () {

      var model = new self.$model(doc);
      if (utils.isFunction(options)) {
        func = options;
        options = {};
      }

      if (!options) {
        options = {};
      }

      var result = yield model.save(options);
      var eventResult = self.notify.call(self, self._getEventName(options, 'insert'), result);
      if (utils.isPromise(eventResult)) {
        yield eventResult;
      }
      utils.callFunc(func, self, undefined, result);
      resolve(result);
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err);
    });
  });
};

ModelManager.prototype.update = function (lookup, doc, options, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    co(function * () {

      if (utils.isFunction(options)) {
        func = options;
        options = {};
      }

      if (!options) {
        options = {};
      }

      var result = yield self.$model.update(lookup, doc, options);
      var eventResult = self.notify(self._getEventName(options, 'update'), result);
      if (utils.isPromise(eventResult)) {
        yield eventResult;
      }
      utils.callFunc(func, self, undefined, result);
      resolve(result);
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err)
    });
  });
};

ModelManager.prototype.remove = function (conditions, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    co(function * () {

      if (utils.isFunction(conditions)) {
        func = conditions;
        conditions = {};
      }

      if (!conditions) {
        conditions = {};
      }

      var result = yield self.$model.remove(conditions);
      var eventResult = self.notify(self._getEventName(options, 'remove'), result);
      if (utils.isPromise(eventResult)) {
        yield eventResult;
      }
      utils.callFunc(func, self, undefined, result);
      resolve(result);
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err)
    })
  });
};

ModelManager.prototype.drop = function (options, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    co(function * () {
      if (options === undefined) {
        options = {};
      }

      var result = yield self.$model.remove();
      var eventResult = self.notify(self._getEventName(options, 'drop'), result);
      if (utils.isPromise(eventResult)) {
        yield eventResult;
      }
      utils.callFunc(func, self, undefined, result);
      resolve(result);
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err)
    })
  });
};

ModelManager.prototype.find = function (lookup, projection, options, func) {

  var self = this;

  return new Promise(function (resolve, reject) {
    co(function * () {
      if (utils.isFunction(lookup)) {
        func = lookup;
        lookup = {};
        projection = {};
        options = {};
      } else if (utils.isFunction(projection)) {
        func = projection;
        projection = {};
        options = {};
      } else if (utils.isFunction(options)) {
        func = options;
        options = {};
      }

      if (!options) {
        options = {};
      }

      if (lookup && lookup.converter) {
        options.converter = lookup.converter;
        delete lookup.converter;
      }

      if (projection && projection.converter) {
        options.converter = projection.converter;
        delete projection.converter;
      }

      var querySet = yield self.$model.find(lookup, projection, options).exec();
      var converter = utils.isFunction(options.converter) ? options.converter : undefined;
      var resultSet = [];

      querySet.forEach(function (item) {
        var it = item;
        if (converter) {
          it = converter(item);
        }
        if (!it) {
          it = item;
        }
        resultSet.push(it);
      });
      utils.callFunc(func, self, undefined, resultSet);
      resolve(resultSet);
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err)
    })
  });
};

ModelManager.prototype.all = function (projection, options, func) {
  return this.find({}, projection, options, func);
};

ModelManager.prototype.count = function (conditions, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    if (utils.isFunction(conditions)) {
      func = conditions;
      conditions = {};
    }

    if (!conditions) {
      conditions = {};
    }

    co(function * () {
      var count = yield self.$model.count(conditions).exec();
      utils.callFunc(func, self, undefined, count);
      resolve(count)
    }).catch(function (err) {
      utils.callFunc(func, self, err);
      reject(err)
    })
  });
};

ModelManager.prototype.start = function () {
  return this.onStart();
};

ModelManager.prototype.onStart = function () {
};

ModelManager.prototype.mapReduce = function (options, func) {
  var self = this;
  return new Promise(function (resolve, reject) {
    var p = self.$model.mapReduce(options);
    p.then(function (model, stats) {
      var name = model.modelName;
      var manager = undefined;
      var managerClass = undefined;
      if (options['manager']) {
        managerClass = options['manager'];
        delete options['manager'];
      }
      if (managerClass !== undefined) {
        manager = new managerClass();
        ModelManager.call(manager, name, model);
        if (!(manager instanceof ModelManager)) {
          util.inherits(managerClass, ModelManager);
        }
      } else {
        manager = new ModelManager(name, model);
      }        
      model.prototype.$manager = model.$manager = manager;
      model.prototype.$name = model.$name = name;
      
      utils.callFunc(func, self, undefined, model, stats);
      resolve({
        model: model,
        stats: stats
      });
    }, function (err) {
      utils.callFunc(func, self, err);
      reject(err);
    });
  });
};

ModelManager.prototype.query = function () {

};

module.exports = ModelManager;
